package cn.moon.server.handler;

import cn.moon.common.config.FrameConfig;
import cn.moon.protocol.ProxyMessageDecoder;
import cn.moon.protocol.ProxyMessageEncoder;
import cn.moon.utils.JSONUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.concurrent.ExecutionException;


/**
 * HTTP处理器
 * 处理管理页面的请求
 */
@Slf4j
public class HttpHandler extends ChannelInboundHandlerAdapter {

    private static final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
    private static final NioEventLoopGroup workerGroup = new NioEventLoopGroup();

    private static Channel bridgeChannel;
    private static int bridgePort = 9000;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {

        if (!(msg instanceof FullHttpRequest)) {
            log.debug("Not FullHttpRequest");
            return;
        }

        FullHttpRequest request = (FullHttpRequest) msg;

        String uri = request.uri();
        String content = byteBufToString(request.content());

        log.debug("uri: {}", uri);
        log.debug("content: {}", content);

        if("/".equals(request.uri())) {
            uri = "/server.html";
        }

        // 处理启动命令
        if(uri.endsWith("/start")){
            handleStart(ctx.channel(), content);
            return;
        }

        // 处理停止命令
        if(uri.endsWith("/stop")){
            handleStop(ctx.channel());
            return;
        }

        // 处理获取端口命令
        if(uri.endsWith("/port")){
            handlePort(ctx.channel());
        }

        // 处理静态资源
        handleResource(ctx.channel(), uri);

    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        Channel channel = ctx.channel();
        closeOnFlush(channel);
    }

    private void closeOnFlush(Channel channel){
        if (channel.isActive()) {
            channel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     * 处理启动命令
     */
    private void handleStart(Channel channel, String content){

        HashMap<String, Integer> contentMap = JSONUtils.parse(content, new TypeReference<HashMap<String, Integer>>(){});
        bridgePort = contentMap.get("port");

        ServerBootstrap bridge = new ServerBootstrap();

        bridge.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                .option(ChannelOption.AUTO_READ, true)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) {
                        ch.pipeline().addLast(new ServiceFrameDecoder(FrameConfig.MAX_FRAME_LENGTH, FrameConfig.LENGTH_FIELD_OFFSET, FrameConfig.LENGTH_FIELD_LENGTH));
                        ch.pipeline().addLast(new ProxyMessageDecoder());
                        ch.pipeline().addLast(new ProxyMessageEncoder(FrameConfig.LENGTH_FIELD_LENGTH));
                        ch.pipeline().addLast(new ServerHandler());
                    }
                });

        if(bridgeChannel != null && bridgeChannel.isOpen()){
            bridgeChannel.close();
        }

        try {
            bridge.bind(bridgePort).addListener((ChannelFutureListener) f -> {
                if (f.isSuccess()) {
                    bridgeChannel = f.channel();
                    responseCommand(channel, true, "服务已启动", null);
                }else {
                    responseCommand(channel, false, "服务启动失败", null);
                }
            }).get();
        } catch (ExecutionException e) {
            responseCommand(channel, false, "服务启动失败, 端口被占用", null);
            log.error("服务启动失败", e);
        }catch (InterruptedException e){
            responseCommand(channel, false, "服务启动失败", null);
            log.error("服务启动失败", e);
        }
    }

    public static void startup(int port){

        bridgePort = port;

        ServerBootstrap bridge = new ServerBootstrap();

        bridge.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                .option(ChannelOption.AUTO_READ, true)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) {
                        ch.pipeline().addLast(new ServiceFrameDecoder(FrameConfig.MAX_FRAME_LENGTH, FrameConfig.LENGTH_FIELD_OFFSET, FrameConfig.LENGTH_FIELD_LENGTH));
                        ch.pipeline().addLast(new ProxyMessageDecoder());
                        ch.pipeline().addLast(new ProxyMessageEncoder(FrameConfig.LENGTH_FIELD_LENGTH));
                        ch.pipeline().addLast(new ServerHandler());
                    }
                });

        try {
            ChannelFuture f = bridge.bind(bridgePort).sync();
            bridgeChannel = f.channel();
            f.get();
        } catch (ExecutionException | InterruptedException e){
            log.error("服务启动失败", e);
        }
    }

    /**
     * 处理停止命令
     */
    private void handleStop(Channel channel){

        if(bridgeChannel != null && bridgeChannel.isOpen()){
            bridgeChannel.close();
        }

        responseCommand(channel, true, "服务已停止", null);

    }

    /**
     * 处理获取端口
     */
    private void handlePort(Channel channel){

        responseCommand(channel, true, "Ok", bridgePort);

    }

    private void responseCommand(Channel channel, boolean isSuccess, String message, Object data){

        HashMap<String, Object> result = new HashMap<>();

        if(isSuccess){
            result.put("code", 0);
        }else {
            result.put("code", -1);
        }

        result.put("message", message);

        if(data != null){
            result.put("data", data);
        }

        byte[] resultBytes = JSONUtils.toJSONString(result).getBytes(CharsetUtil.UTF_8);

        FullHttpResponse response = new DefaultFullHttpResponse(
                HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(resultBytes));

        response.headers().set("Content-Length", resultBytes.length);
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/javascript");

        channel.write(response);

        closeOnFlush(channel);
    }

    private void handleResource(Channel channel, String uri){

        InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("static" + uri);

        if(is == null){
            closeOnFlush(channel);
            return;
        }

        PooledByteBufAllocator allocator = new PooledByteBufAllocator();
        ByteBuf buf = allocator.buffer();
        byte[] bytes = new byte[1024 * 200];

        int length = 0;

        try{
            int len;
            while((len = is.read(bytes)) != -1){
                length += len;
                buf.writeBytes(bytes, 0, len);
            }

        } catch (IOException e) {
            log.error("读取静态资源出错", e);
            return;
        }

        FullHttpResponse response = new DefaultFullHttpResponse(
                HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);

        response.headers().set("Content-Length", length);

        if(uri.endsWith(".html")){
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_HTML);
        }else if(uri.endsWith(".css")){
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_CSS);
        }else if(uri.endsWith(".js")){
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/javascript");
        }else if(uri.endsWith(".svg")){
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "image/svg+xml");
        }

        channel.write(response);

        closeOnFlush(channel);
    }

    private String byteBufToString(ByteBuf byteBuf){

        byte[] bytes = new byte[byteBuf.readableBytes()];

        byteBuf.readBytes(bytes);

        return new String(bytes, CharsetUtil.UTF_8);

    }

}
